Skip to content

Fix: Report unboxing.of.nullable in conditional expressions with primitive result#7433

Open
Suvrat1629 wants to merge 6 commits intotypetools:masterfrom
Suvrat1629:false-neg-6849
Open

Fix: Report unboxing.of.nullable in conditional expressions with primitive result#7433
Suvrat1629 wants to merge 6 commits intotypetools:masterfrom
Suvrat1629:false-neg-6849

Conversation

@Suvrat1629
Copy link
Copy Markdown

  • Fixes false negative where a @Nullable boxed value in a ternary expression with primitive result type was unboxed without warning.
  • Added check in NullnessVisitor.visitConditionalExpression to detect when javac performs unboxing conversion on one or both operands.
  • Added regression test Issue6849.java.

Closes #6849

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ff2d4172-3caa-4226-b4f6-e8d1d8b03649

📥 Commits

Reviewing files that changed from the base of the PR and between 164f5e6 and 8ba7018.

📒 Files selected for processing (1)
  • checker/tests/nullness/Issue6849.java

📝 Walkthrough

Walkthrough

visitConditionalExpression in NullnessVisitor now performs unboxing-aware nullness checks (UNBOXING_OF_NULLABLE) on the true and false operands when the conditional expression has a primitive target type but one or both operands are reference types. If an unboxing check reports a nullness error for an operand, the method returns early to avoid cascading diagnostics; otherwise it continues with the existing flow. A new test Issue6849.java was added demonstrating a ternary expression combining a nullable reference and a primitive constant to exercise this check.

Suggested reviewers

  • smillst
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully implements the objective from issue #6849: detecting and reporting unboxing.of.nullable errors in conditional expressions with primitive result types.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the false negative in conditional expressions; no unrelated modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11643b3 and 0994c6a.

📒 Files selected for processing (2)
  • checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java
  • checker/tests/nullness/Issue6849.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java
🧬 Code graph analysis (1)
checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java (1)
javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java (1)
  • TypesUtils (55-1555)
🔇 Additional comments (13)
checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java (12)

71-72: Formatting-only change — no functional impact.


144-146: Formatting-only change — no functional impact.


157-163: Formatting-only change — no functional impact.


192-196: Formatting-only change — no functional impact.


221-226: Formatting-only change — no functional impact.


394-400: Formatting-only change — no functional impact.


442-444: Formatting-only change — no functional impact.


571-571: Formatting-only change — no functional impact.


699-701: Formatting-only change — no functional impact.


825-836: Formatting-only change — no functional impact.


841-842: Formatting-only change — no functional impact.


787-816: Logic correctly detects unboxing in conditional expressions, verified by existing tests.

The implementation properly:

  1. Checks if the overall conditional result type is primitive
  2. Identifies operands that need unboxing (reference types being assigned to primitive)
  3. Reports unboxing.of.nullable when nullable references would be unboxed
  4. Returns early after reporting an error to avoid cascading errors (consistent with visitTypeCast pattern)

The new logic is exercised by existing tests in Issue6849.java and Issue3614.java, which verify that unboxing errors are correctly reported in conditional expressions (e.g., int y = ((true) ? method() : 10) when method returns @Nullable Integer).

When both operands need unboxing and both are nullable, only the first error is reported due to the early return. This is acceptable—the cascading error prevention is intentional, though users would need to re-run after fixing the first issue to see subsequent errors.

checker/tests/nullness/Issue6849.java (1)

6-8: Consider adding @Nullable annotation to the return type for explicitness.

The method m returns T, which when instantiated as @Nullable Integer correctly returns a nullable value. The current implementation is correct, but you might consider whether adding a test case that explicitly shows the return type could help document the behavior.

Comment thread checker/tests/nullness/Issue6849.java
@smillst smillst self-assigned this Jan 15, 2026
@Suvrat1629
Copy link
Copy Markdown
Author

@smillst gentle ping please take a look

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java`:
- Around line 780-805: Replace the explicit primitive checks that call
TypesUtils.isPrimitive(TreeUtils.typeOf(...)) with the class's existing helper
isPrimitive(ExpressionTree) for consistency and readability inside
NullnessVisitor (the conditional-expression handling that computes
trueNeedsUnboxing/falseNeedsUnboxing); keep the rest of the logic intact (still
call checkForNullability on trueExpr/falseExpr with UNBOXING_OF_NULLABLE and
preserve the early-return behavior when checkForNullability returns false) so
only the primitive-test expressions are swapped to use
isPrimitive(ExpressionTree).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f60011f and 164f5e6.

📒 Files selected for processing (1)
  • checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java

Comment on lines +780 to +805
// If the overall conditional expression has a primitive Java type but one or both
// operand expressions are reference types, then unboxing will occur as part of
// evaluating the conditional. In that case, check the operand(s) for possible
// nullness (unboxing.of.nullable).
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree);
// Only check for unboxing when javac has chosen a primitive underlying type
// for the conditional expression, but one or both operands are non-primitive.
if (type.getKind().isPrimitive()) {
ExpressionTree trueExpr = tree.getTrueExpression();
ExpressionTree falseExpr = tree.getFalseExpression();
boolean trueNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(trueExpr));
boolean falseNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(falseExpr));

if (trueNeedsUnboxing) {
if (!checkForNullability(trueExpr, UNBOXING_OF_NULLABLE)) {
// If we reported an unboxing.of.nullable error for the true arm, stop
// further checking to avoid cascading errors.
return null;
}
}
if (falseNeedsUnboxing) {
if (!checkForNullability(falseExpr, UNBOXING_OF_NULLABLE)) {
return null;
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Logic is correct; consider using the existing helper method for consistency.

The fix correctly identifies when javac implicitly unboxes a reference-typed operand in a conditional expression with a primitive result type and reports unboxing.of.nullable when appropriate. The early return pattern is consistent with visitTypeCast (lines 509-513).

Minor suggestion: The existing isPrimitive(ExpressionTree) helper method (lines 714-716) could be reused for slightly more readable and consistent code.

♻️ Suggested refactor to use existing helper
     if (type.getKind().isPrimitive()) {
       ExpressionTree trueExpr = tree.getTrueExpression();
       ExpressionTree falseExpr = tree.getFalseExpression();
-      boolean trueNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(trueExpr));
-      boolean falseNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(falseExpr));
+      boolean trueNeedsUnboxing = !isPrimitive(trueExpr);
+      boolean falseNeedsUnboxing = !isPrimitive(falseExpr);
 
       if (trueNeedsUnboxing) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If the overall conditional expression has a primitive Java type but one or both
// operand expressions are reference types, then unboxing will occur as part of
// evaluating the conditional. In that case, check the operand(s) for possible
// nullness (unboxing.of.nullable).
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree);
// Only check for unboxing when javac has chosen a primitive underlying type
// for the conditional expression, but one or both operands are non-primitive.
if (type.getKind().isPrimitive()) {
ExpressionTree trueExpr = tree.getTrueExpression();
ExpressionTree falseExpr = tree.getFalseExpression();
boolean trueNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(trueExpr));
boolean falseNeedsUnboxing = !TypesUtils.isPrimitive(TreeUtils.typeOf(falseExpr));
if (trueNeedsUnboxing) {
if (!checkForNullability(trueExpr, UNBOXING_OF_NULLABLE)) {
// If we reported an unboxing.of.nullable error for the true arm, stop
// further checking to avoid cascading errors.
return null;
}
}
if (falseNeedsUnboxing) {
if (!checkForNullability(falseExpr, UNBOXING_OF_NULLABLE)) {
return null;
}
}
}
// If the overall conditional expression has a primitive Java type but one or both
// operand expressions are reference types, then unboxing will occur as part of
// evaluating the conditional. In that case, check the operand(s) for possible
// nullness (unboxing.of.nullable).
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree);
// Only check for unboxing when javac has chosen a primitive underlying type
// for the conditional expression, but one or both operands are non-primitive.
if (type.getKind().isPrimitive()) {
ExpressionTree trueExpr = tree.getTrueExpression();
ExpressionTree falseExpr = tree.getFalseExpression();
boolean trueNeedsUnboxing = !isPrimitive(trueExpr);
boolean falseNeedsUnboxing = !isPrimitive(falseExpr);
if (trueNeedsUnboxing) {
if (!checkForNullability(trueExpr, UNBOXING_OF_NULLABLE)) {
// If we reported an unboxing.of.nullable error for the true arm, stop
// further checking to avoid cascading errors.
return null;
}
}
if (falseNeedsUnboxing) {
if (!checkForNullability(falseExpr, UNBOXING_OF_NULLABLE)) {
return null;
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java`
around lines 780 - 805, Replace the explicit primitive checks that call
TypesUtils.isPrimitive(TreeUtils.typeOf(...)) with the class's existing helper
isPrimitive(ExpressionTree) for consistency and readability inside
NullnessVisitor (the conditional-expression handling that computes
trueNeedsUnboxing/falseNeedsUnboxing); keep the rest of the logic intact (still
call checkForNullability on trueExpr/falseExpr with UNBOXING_OF_NULLABLE and
preserve the early-return behavior when checkForNullability returns false) so
only the primitive-test expressions are swapped to use
isPrimitive(ExpressionTree).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

false negative when using ternary operator

3 participants